-
Notifications
You must be signed in to change notification settings - Fork 344
Added ScopeListener interface to the ThreadLocalScopeManager #336
base: master
Are you sure you want to change the base?
Conversation
/** | ||
* Called when outermost scope was deactivated. | ||
*/ | ||
void onClose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not pass the span here too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then it would make sense to call onActivate
with previous span as well (optionally null). I'd think it is better to keep the interface as simple as possible. But its no problem to implement it of course :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure why previous span is relevant - if we're on an executor thread that span may be completely unrelated to the one being activated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but previous span is the one being deactivated - same situation as onClose. So, if you don't need deactivated span in onActivate, you should not need it in onClose either.
scopeManager.listener.onActivate(toRestore.wrapped); | ||
} else { | ||
scopeManager.tlsScope.remove(); | ||
scopeManager.listener.onClose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be done earlier imo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure? Listener code will then execute in the context of the previous span, not the one passed in as argument. Same applies to onClose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, the "previous span" is not really relevant. This function closes the current scope, so it should invoke listener.close(currentScope.span)
. Then it can do the housekeeping of the stack and re-activation of the previous span.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, maybe we misunderstood each other. Were you talking about onClose or onActivate?
onClose
can be called before scopeManager.tlsScope.remove();
and it makes sense. I'll update that.
Calling onActivate
before scopeManager.tlsScope.set(toRestore);
does not make sense - onActivate would run still with active previous span.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the second thought, what about renaming methods to onActivated
and onClosed
, and really call them both after change has happened? After all, this is listener to change, not interceptor. I would naturally expect, that state transition has already happened, when I'm receiving the call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
I think this is fine, aligning with the recent changes on 0.32 around exposing active |
I'm wondering if supporting multiple listeners could be useful ;) |
Multiple listeners are already possible via composition. |
/** | ||
* Called when outermost scope was deactivated. | ||
*/ | ||
void onClosed(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think it needs to accept the span represented by the scope being closed.
scopeManager.listener.onActivated(toRestore.wrapped); | ||
} else { | ||
scopeManager.tlsScope.remove(); | ||
scopeManager.listener.onClosed(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the previous comments got lost - the onClosed must be called regardless of the previous span presence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And thats exactly what i don't want to do, see discussion in #334.
Most common use-case is setting MDC (or ThreadContext for log4j) - and that uses copy-on-write immutable maps.
So, having to do following sequence:
- onActivated(1) - set MDC (copy internal map)
- onClose(1) - delete MDC (copy internal map) - useless
- onActivated(2) - set MDC (copy internal map)
- onClose(2) - delete MDC (copy internal map) - useless
- onActivated(1) - set MDC (copy internal map)
- onClose(1) - delete MDC (copy internal map) - needed
wastes resources. Consumer cannot know, whether onActivated is coming right after onClose.
Thats why originally wanted only protected method for simple extension, or single method interface, so consumer can decide, what is best. This API should be simplistic, if you need something complex, you can copy whole class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW thats why I originally proposed single method interface, with possible null as argument - its ugly, but efficient and versatile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're making assumption that MDC is the only use case. If you don't want to clear MDC from onClosed(), you don't have to, it does not negate the usefulness of the callback for other use cases. For example, see census-instrumentation/opencensus-specs#247
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have to clear it in onClosed(), because you can't leave it set on last deactivation (updated example above, sorry I left it out, see its 6) step).
Not saying its only use case, but its use case that should be supported. Give users freedom to do what they need, don't force them to follow exact and strict pattern.
Only model, that would actually fulfill both use-cases IMO, is ugly but flexible:
interface ScopeListener {
void onSpanChanged(@Nullable Span activeSpan, @Nullable Span deactivatedSpan);
}
Or, I would revisit protected method and simply inheritance - do everyone whatever they want.
class ThreadLocalScopeManager {
protected void setThreadScope(@Nullable ThreadLocalScope scope) {
this.tlsScope.set(scope);
}
}
this can serve as "around advice". And it would make the class extensible in many ways, which I don't think is a bad thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved discussion to bottom, it will get lost here on another push.
@yurishkuro sure, but was wondering if we should support this out-of-the-box ;) (not an important thing, just curious about the possibility) |
We can always add it later, it's a utility class that would dispatch to 2+ underlying listeners. Nobody asked for it yet. |
@yurishkuro Fair enough, sounds great to me ;) |
So, with the ScopeListener, I would need to track the association between an onActivate and a close of a scope myself, most likely with a thread local. For example, if I start a JFR event in a scope, I would have to add it in a thread local, to close the right one once close is hit. Brave, for example, uses the concept of span and scope decorators. Upon activation, the scope decorator would expect a new scope to be returned (see ScopeDecorator#decorateScope(...)), into which the event could be pushed and closed when the scope itself is closed. No thread locals required. |
@thegreystone I like the decorator idea and I think it has come up before. |
* | ||
* @param span Activated span. Never null. | ||
*/ | ||
void onActivated(Span span); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the method name should reflect whether the activation happens before or after invoking this method. I'd propose beforeActivated
and afterClosed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree on this. It's clearer having a before
or after
prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call
final ThreadLocal<ThreadLocalScope> tlsScope = new ThreadLocal<ThreadLocalScope>(); | ||
final ScopeListener listener; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider making it a list or adding a CompoundScopeListener
, which maintains a list of ScopeListeners
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
composite listener can be another PR, imo, if someone actually needs it.
Moving discussion to bottom. We've identified 2 requirements:
Only possible solution I can think of is a bit ugly but universal interface ScopeListener {
void onSpanChanged(@Nullable Span activeSpan, @Nullable Span deactivatedSpan);
} I can't think of use case, where I would need handler before action. If so, you can always replace whole ScopeManager with your own.. Which should still be normal option for anyone requesting special behavior. I just wanted simple way to add configuring MDC for logging, without need to copy-paste scope manager into every app. |
|
||
import io.opentracing.Span; | ||
|
||
public interface NoopScopeListener extends ScopeListener { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we really need to expose this one as public.
@mdvorak I don't think we have an agreement on (2) to have a single operation. Using lambda or 2-method class is, imo, a very minor thing compared to the cleanliness and intuitiveness of the API. |
Sorry I mentioned lambda originally, but read carefully previous post. Problem for two-method handler is an implementation, where deactivation always called before activation creates performance (or other) problems. You force implementation to have two separate handlers, when it actually needs to handle both at one step. |
Here's a suggestion... Have the What does the prior art look like? How does this compare with Brave/Census/etc? |
What about adding an argument for the previously active/now active span.
@thegreystone This should eliminate the need for maintaining a separate @mdvorak would that also solve your MDC use case? You will only have to remove the MDC context if |
@felixbarny Yes, it would be possible to create efficient handler, but I still find it very complicated to do very simple thing. Also, relying on behavior, where that when as for 1) I already proposed to provide both previous and next spans, so I agree with you on first point. |
How is this different when having a single method?
Not sure what is implementation specific here - the behavior is clearly specified and does not depend on the implementation. |
Not sure how this would help. I would still need to propagate the event from beforeActivate to afterDeactivate somehow, and the only way I would know how is to use a thread local (in the scope case; in the SpanListener case all bets are off). |
True, doing this in a general fashion requires a bit of extra consideration. Brave simply returns implementations of their own scope interface, and assume they are the only player in town.
If we have decorators, we probably do not need listeners. And if we have listeners, it will seem a bit excessive to have decorators too. It may be worth it to consider the options. Anyone from Brave involved in OpenTracing? |
Here are some Brave decorators: The JFR use case is the closest to my heart. |
Scope decorators are great, and that was my first idea, except with current implementation it does not work reliably, because of this if: public class ThreadLocalScope implements Scope {
@Override
public void close() {
if (scopeManager.tlsScope.get() != this) {
// This shouldn't happen if users call methods in the expected order. Bail out.
return;
} decorator can never know, whether scope was actually closed or not. |
@mdvorak Besides what others already commented/asked, I suggest you cooking a small case under |
Don't let that influence the listener vs. decorator discussion please. If that's a 'defect' of the current scope manager implementation, we could address that separately from the decorator issue. We could update scope manager to handle out-of-order closing in a predictable manner for example. |
I misunderstood what the problem is. Looking at brave's JfrScopeDecorator I understand now. You have to have a reference to the I see two designs to achieve that: Brave-like scope wrappers
Context object Introducing a span context object which allows to attach custom key/value pairs to it. You can add custom context objects in the before callback and retrieve them from the map in the after callback.
Example public class ExampleScopeListener implements ScopeListener {
@Override
public void beforeActivate(Span spanToActivate, ScopeContext context) {
context.add("foo", "bar");
}
@Override
public void afterDeactivate(Span deactivatedSpan, ScopeContext context) {
String foo = context.get("foo");
}
} |
Btw, |
Can you link to that discussion or summarize some of the discussed use cases? |
Hey @felixbarny This is the Issue: opentracing/specification#114 |
Since this has been dormant for a while, I wanted to mention that there is an opentracing-contrib module which can do sth quite similar: https://github.com/opentracing-contrib/java-api-extensions |
Not really. You can't observe Scopes with that.
…On Tue, 24 Dec 2019, 08:52 Dietrich Schulten, ***@***.***> wrote:
Since this has been dormant for a while, I wanted to mention that there is
an opentracing-contrib module which can do sth quite similar:
https://github.com/opentracing-contrib/java-api-extensions
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#336?email_source=notifications&email_token=AADI7HO4VLGIIUKFOWASGQTQ2G5TXA5CNFSM4G6X6CB2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEHSXDIA#issuecomment-568684960>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AADI7HJ7OVCG6KD7QJJ4AWTQ2G5TXANCNFSM4G6X6CBQ>
.
|
That is true, you observe when a Out of curiosity: What is the compelling reason why one needs to observe a |
I believe I discussed that somewhere - Span is completely different from Scope: |
ScopeListener
ThreadLocalScopeManager
NoopScopeListener
to be used as defaultIs there anything else missing?
Also, I've created signature of onActivate, thaking directly span:
Because consumers should not actually need the scope, only its span - but it is a little weird, having Span in the interface called ScopeListener. What do you think? I have already prepared commit to change it to
void onActivate(Scope scope);